#include "Font.h"
#include "sdl_gltexture.h"
#include <SDL_ttf.h>
#include <sstream>
#include <vector>
#include <iostream>
#include <SDL.h>
#include <cmath>

namespace Lame3d
{
	// Universal thang
	const char* alphabet = " !\"#$%&'()*+,-./0123456789;:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}";
	//const char* alphabet = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
	//const char* alphabet = " !\"#$%&'()*+,-./0123456789;:<=>?@AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz{|}[\\]^_`";
	//const char* alphabet = "                                 ABCDEFGHIJKLMNOPQRSTUVWXYZ      abcdefghijklmnopqrstuvwxyz   ";

	// Font::Glyph Implementation
	Font::Glyph::Glyph()
	{
	}
	
	Font::Glyph::~Glyph()
	{
		glDeleteLists(list, 1);
	}
	
	void Font::Glyph::Init(const Point2d &uLeft, const Point2d &lRight, const Point2d &dims, float adv, float drop)
	{
		advance = adv;
		
		printf("uLeft: %f %f ... lRight: %f %f ... dims: %f %f ... adv: %f .. drop: %f \n",
			uLeft[0], uLeft[1], lRight[0], lRight[1], dims[0], dims[1], adv, drop );
		
		list = glGenLists(1);
		if(list == 0) {
			throw "Error creating GL RenderList for glyph.";
		}
		
		// generate list!
		glNewList(list, GL_COMPILE);
		glBegin(GL_QUADS);
		
		glTexCoord2f(lRight[0], lRight[1]);
		glVertex2f(dims[0], -dims[1]-drop);
		
		glTexCoord2f(lRight[0], uLeft[1]);
		glVertex2f(dims[0], 0-drop);
		
		glTexCoord2f(uLeft[0], uLeft[1]);
		glVertex2f(0, 0-drop);
			
		glTexCoord2f(uLeft[0], lRight[1]);
		glVertex2f(0, -dims[1]-drop);		
		
		glEnd();
		glEndList();
	}
	
	void Font::Glyph::Render()
	{
		glCallList(list);
	}
	
	void Font::Glyph::Advance()
	{
		glTranslatef(advance, 0, 0);
	}
	
	float Font::Glyph::GetAdvance() const
	{
		return advance;
	}
	
	// Font Implementation
	Font::Font(const File &f, const DisplayManager &dM, const Color &c, int size)
			: file(f), refCount(0), glyphs(NULL), fontSize(size), displayManager(dM), color(c)
	{
	}
	
	Font::~Font()
	{
		if(refCount > 0) {
			throw "Attempting to destroy font with nonzero references!";
		}
	}
	
	Font::Handle Font::GetHandle()
	{
		return Handle(*this);
	}
	
	struct _glyphRow
	{
		int rowLength;
		std::vector<char> characters;
		
		_glyphRow()
				: rowLength(0)
		{
		}
	};

	void Font::load()
	{
		TTF_Font *fg = NULL;
		
		if((fg = TTF_OpenFont(file.GetPath().c_str(), fontSize))==NULL) {
			throw "Error loading font in TTF_OpenFont.";
		}
		
		int lineHeightInt = TTF_FontHeight(fg);
		lineHeight = float(lineHeightInt) / float(displayManager.CurrentConfig().Height);
		
		int height = lineHeightInt*2;
		
		std::vector<_glyphRow> glyphRows;
		glyphRows.push_back(_glyphRow());
		
		for(int i = 0; i<94; ++i) {
			int width;
			int minX;
			int maxX;
			if(TTF_GlyphMetrics(fg, alphabet[i], &minX, &maxX, NULL, NULL, NULL) ==  -1) {
				std::cerr << "Couldn't find metrics for character: " << alphabet[i] << std::endl;
				throw "Couldn't get glyph metrics during font creation";
			}
			width = maxX - minX;
			
			// find minimum glyphRow...
			int addRow = 0;
			for(unsigned int j=0; j<glyphRows.size(); ++j) {
				_glyphRow &r = glyphRows[j];
				if(r.rowLength < glyphRows[addRow].rowLength) {
					addRow = j;
				}
			}
			
			// check to see if we should add a new row or add to this row
			if(height < glyphRows[addRow].rowLength) {
				// Add new row
				_glyphRow r;
				r.characters.push_back( alphabet[i] );
				r.rowLength += width;
				glyphRows.push_back(r);
				height += lineHeightInt*2;
			} else {
				// Add to current row
				glyphRows[addRow].characters.push_back( alphabet[i] );
				glyphRows[addRow].rowLength += width + 2;
			}
		}
		
		int maxWidth = 0;
		for(unsigned int i=0; i<glyphRows.size(); ++i) {
			if(glyphRows[i].rowLength > maxWidth) {
				maxWidth = glyphRows[i].rowLength;
			}
		}
		
		// Create image to blit glyphs to.
		int imgWidth = power_of_two(maxWidth);
		int imgHeight = power_of_two(height);
		
		// Debugging information!
		std::cout << "Glyph map dimensions: " << imgWidth << " by " << imgHeight << std::endl;
		for(unsigned int i=0; i<glyphRows.size(); ++i) {
			for(unsigned int j=0; j<glyphRows[i].characters.size(); ++j) {
				printf("%c", glyphRows[i].characters[j]);
			}
			std::cout << std::endl;
		} 
		
		SDL_Surface *image = SDL_CreateRGBSurface(
				SDL_SWSURFACE,
				imgWidth, imgHeight,
				SDL_GetVideoInfo()->vfmt->BitsPerPixel,
				SDL_GetVideoInfo()->vfmt->Rmask,
				SDL_GetVideoInfo()->vfmt->Gmask,
				SDL_GetVideoInfo()->vfmt->Bmask,
				SDL_GetVideoInfo()->vfmt->Amask
		);
		SDL_Color c = color.SDLColor();
		if(c.r < (0x1 << 2) && c.g < (0x1<<2) && c.b < (0x1<<2)) {
			c.r = 0xAE;
			c.g = 0xAE;
			c.b = 0xAE;
		} else {
			if(c.r <= (0x1 << 2)) {
				c.r = (c.r+0x3) << 3; 
			} else {
				c.r >>= 2;
			}
			if(c.g <= (0x1 << 2)) {
				c.g = (c.g+0x3) << 3; 
			} else {
				c.g >>= 2;
			}
			if(c.b <= (0x1 << 2)) {
				c.b = (c.b+0x3) << 3; 
			} else {
				c.b >>= 2;
			}
		}
		
		int opposite = (c.r<<SDL_GetVideoInfo()->vfmt->Rshift |
			c.g<<SDL_GetVideoInfo()->vfmt->Gshift |
			c.b<<SDL_GetVideoInfo()->vfmt->Bshift /*| 
			0x00<<SDL_GetVideoInfo()->vfmt->Ashift*/);
		
		SDL_SetColorKey(image, SDL_SRCCOLORKEY, opposite);
		SDL_FillRect(image, NULL, opposite);
		SDL_SetAlpha(image, 0, 0);
	    
	    // blit some glyphs!
	    for(unsigned int i=0; i<glyphRows.size(); ++i) {
	    	_glyphRow &currentRow = glyphRows[i];
	    	int y = i * lineHeightInt * 2;
	    	int x = 0;
	    	for(unsigned int j=0; j<currentRow.characters.size(); ++j) {
	    		SDL_Surface *glyphSurface = TTF_RenderGlyph_Blended(fg, currentRow.characters[j], color.SDLColor());
	    		if(glyphSurface == NULL) {
	    			throw "NULL surface returned for glyph!";
	    		}
	    		
	    		int minX, maxX, minY, maxY, advance;
	    		if(TTF_GlyphMetrics(fg, currentRow.characters[j], &minX, &maxX, &minY, &maxY, &advance) == -1) {
	    			std::cerr << "Error getting glyph metrics for glyph: " << currentRow.characters[j] << " (glyph blit stage)" << std::endl; 
	    			throw "Error getting glyph metrics!";
	    		}
	    		
	    		int targetY = y + lineHeightInt - maxY;
	    		SDL_Rect dstRect;
	    		dstRect.x = x;
	    		dstRect.y = targetY;
	    		dstRect.w = maxX - minX;
	    		dstRect.h = maxY - minY;
	    		
	    		if(SDL_BlitSurface(glyphSurface, NULL, image, &dstRect) == -1) {
	    			throw "Blitting error when greating glyph map.";
	    		}
	    		
	    		SDL_FreeSurface(glyphSurface);
	    		
	    		x += dstRect.w;
	    		printf("width: %d\n", dstRect.w);
	    	}
	    }
	    
	    // save bmp of glyphmap (debugging)
	   	//SDL_SaveBMP(image, "glyphmap.bmp");
	    
	    // convert to OpenGL texture and free old texture!
		texture = SDL_GL_LoadTexture(image, NULL);
		glBindTexture(GL_TEXTURE_2D, texture);
		
		SDL_FreeSurface(image);
		
		// create glyph objects!
		glyphs = new Glyph[94];
		for(unsigned int i=0; i < glyphRows.size(); ++i) {
			int y = imgHeight - i*lineHeightInt*2;
	    	int x = 0;
			for(unsigned int j=0; j < glyphRows[i].characters.size(); ++j) {
				int minX, maxX, minY, maxY, advance;
	    		if(TTF_GlyphMetrics(fg, glyphRows[i].characters[j], &minX, &maxX, &minY, &maxY, &advance) == -1) {
	    			std::cerr << "Error getting glyph metrics for glyph: " << glyphRows[i].characters[j] << " (gl texture map stage)" << std::endl; 
	    			throw "Error getting glyph metrics!";
	    		}
	    		
	    		int width = maxX - minX;
	    			    		
	    		Point2d uLeft( float(x)/float(imgWidth), float(y)/float(imgHeight) );
	    		Point2d lRight( float(x + width)/float(imgWidth), float(y-lineHeightInt+minY)/float(imgHeight) );
	    		Point2d dims( 	float(width)/float(displayManager.CurrentConfig().Width)*displayManager.Ratio(),
	    						float(lineHeightInt-minY)/float(displayManager.CurrentConfig().Height) );
	    		float advanceFloat = float(advance) / float(displayManager.CurrentConfig().Width) * displayManager.Ratio();
	    		glyphs[ glyphRows[i].characters[j] - ' '].Init(uLeft, lRight, dims, advanceFloat, 0 /*-float(minY)/float(displayManager.CurrentConfig().Height)*/);
	    		
	    		x += width;
			}
		}
		
		TTF_CloseFont(fg);
	}
	
	void Font::unload()
	{
		glDeleteTextures(1, &texture);
		delete[] glyphs;
	}
		
	// Handle Implementation
	Font::Handle::Handle(Font &f)
			: font(&f)
	{
		bind();
	}
	
	Font::Handle::~Handle()
	{
		unbind();
	}
	
	void Font::Handle::Print(const std::string &message, const Point2d &dims, int index)
	{
		Point2d remaining(dims);
		std::string msg(message);		
		int lIt = index;
		char lCh = message[lIt];
		int rIt = index+1;
		int size = msg.size();
		char rCh = 0;
		
		// 1st pass: formatting and bounds checking
		// find a space on lIt
		while(lIt < size && lCh != ' ') {
			if(lCh == '\n') {
				remaining[0] = dims[0];
				remaining[1] -= font->lineHeight;
			} else {
				remaining[0] -= font->glyphs[lCh - ' '].GetAdvance();
			}
			++lIt;
			lCh = msg[lIt];
		}
		if(lCh == ' ') {
			remaining[0] -= font->glyphs[0].GetAdvance();
		}
		// move rIt until the end.
		rIt = lIt + 1;
		bool loop = true;
		while(loop) {
			rCh = msg[rIt];
			float oldRemainingX = remaining[0];
			while(loop && rCh != ' ' && rCh != '\n') {
				remaining[0] -= font->glyphs[rCh - ' '].GetAdvance();
				loop = (rIt < size) && (remaining[1] > font->lineHeight) && (remaining[0] > 0);
				++rIt;
				rCh = message[rIt];
			} 
			if(rCh == ' ') {
				remaining[0] -= font->glyphs[0].GetAdvance();
				lIt = rIt;
				oldRemainingX = remaining[0];
			} else if(rCh == '\n') {
				// new line
				remaining[0] = dims[0];
				remaining[1] -= font->lineHeight;
				lIt = rIt;
				oldRemainingX = remaining[0];
			} 
			if(remaining[0] <= 0) {
				// line wrap
				msg[lIt] = '\n';
				remaining[0] = dims[0] - oldRemainingX;
				remaining[1] -= font->lineHeight;
			}
			++rIt;
			loop = (rIt < size) && (remaining[1] > font->lineHeight);
		}
		
		/*
		if(remaining[1] <= font->lineHeight) {
			if(size >= 3) {
				msg[rIt-1] = '.';
				msg[rIt-2] = '.';
				msg[rIt-3] = '.';
			}
		}
		*/
		/*
		if(rIt > size) {
			rIt = size;
		}
		*/
		
		// 2nd pass: print - dumb, very little checking.
		glPushMatrix();
		glBindTexture(GL_TEXTURE_2D, font->texture);
		glDisable(GL_DEPTH_TEST);
		int numLines = 0;
		for(lIt = index; lIt != rIt; ++lIt) {
			if(msg[lIt] >= ' ' && msg[lIt] <= '}') {
				print(msg[lIt]);
			}
			if(msg[lIt] == '\n') {
				glPopMatrix();
				glPushMatrix();
				++numLines;
				for(int i = 0; i<numLines; ++i) {
					NewLine();
				}
			}
		}
		glEnable(GL_DEPTH_TEST);
		glPopMatrix();
	}
	
	void Font::Handle::printSmart(const std::string &message, float width) {
//		printf("print smart\n");
		std::string msg(message);
		std::string::iterator l;
		std::string::iterator r;
		float lWidth = 0;
		float rWidth = 0;
		// set l
		l = msg.begin();
		while(l != msg.end() && *l != ' ') {
			lWidth += font->glyphs[*l - ' '].GetAdvance();
			++l;
		}
		while(l != msg.end()) {
			// l set :-)
			rWidth = lWidth;
			r = l;
			++r;
			while(r != msg.end() && *r != ' ') {
				rWidth += font->glyphs[*r - ' '].GetAdvance();
				++r;
			}
			if(*r == ' ') {
				rWidth += font->glyphs[*r - ' '].GetAdvance();
			}
			if(rWidth >= width) { // magic scaling factor!
				*l = '\n';
				rWidth = rWidth - lWidth;
			}
			lWidth = rWidth;
			l = r;
		}
		glPushMatrix();
		int numLines = 0;
		for(l = msg.begin(); l!=msg.end(); ++l) {
			if(*l >= ' ' && *l <= '}') {
				print(*l);
			}
			if(*l == '\n') {
				glPopMatrix();
				glPushMatrix();
				++numLines;
				for(int i = 0; i<numLines; ++i) {
					NewLine();
				}
			}
		}
		glPopMatrix();
	}
	
	float Font::Handle::print(char c)
	{
		Glyph &g = font->glyphs[c - ' '];
		g.Render();
		g.Advance();
		return g.GetAdvance();
	}
	
	void Font::Handle::NewLine()
	{
		glTranslatef(0, -font->lineHeight, 0);
	}
	
	float Font::Handle::Width(const std::string &message) const
	{
		float width = 0;
		for(unsigned int i=0; i<message.size(); ++i) {
			const Glyph &g = font->glyphs[message[i] - ' '];
			width += g.GetAdvance();
		}
		return width;
	}
	
	float Font::Handle::LineHeight() const
	{
		return font->lineHeight;
	}
	
	Font::Handle& Font::Handle::operator=(const Handle &h)
	{
		unbind();
		font = h.font;
		bind();
		return *this;
	}
	
	void Font::Handle::bind()
	{
		if(font->refCount++ < 1) {
			font->load();
		}
	}
	
	void Font::Handle::unbind()
	{
		if(--font->refCount < 1) {
			font->unload();
		}
	}
	
	/* FormattedString */
	FormattedString::FormattedString(const std::string &s, Font::Handle &h, float width, float height, int cursor, bool wrap)
			: string(s), handle(h)
	{
		format(width, height, cursor, wrap);
	}
	
	FormattedString::~FormattedString()
	{
	}
	
	const std::string& FormattedString::String() const
	{
		return string;
	}
	
	float FormattedString::Width() const
	{
		return width;
	}
	
	float FormattedString::Height() const
	{
		return height;
	}
	
	int FormattedString::Lines() const
	{
		return lines;
	}
	
	void FormattedString::Render()
	{
		glPushMatrix();
		glBindTexture(GL_TEXTURE_2D, handle.font->texture);
		glDisable(GL_DEPTH_TEST);
		int numLines = 0;
		for(int lIdx = leftCursor; lIdx <= rightCursor; ++lIdx) {
			if(string[lIdx] == '\n') {
				glPopMatrix();
				glPushMatrix();
				++numLines;
				for(int i = 0; i<numLines; ++i) {
					handle.NewLine();
				}
			} else {
				handle.print(string[lIdx]);
			}
		}
		glEnable(GL_DEPTH_TEST);
		glPopMatrix();
	}
	
	void FormattedString::format(float w, float h, int cursor, bool wrap)
	{
		// ( I've written this algorithm too many fucking times )
		
		// Lame does not support hard breaks.
		int len = string.size();
		
		// 1st step: insert soft breaks according to width setting.
		float cWidth = 0;
		int lIdx = 0;
		int rIdx = -1;
		float maxWidth = 0;
		
		if(wrap) {
			// 1.1 - set left iterator.
			do {
				for(; lIdx < len && string[lIdx] != ' ' && cWidth < w; ++lIdx) {
					cWidth += handle.font->glyphs[ string[lIdx] - ' ' ].GetAdvance();
				}
				if(cWidth >= w && lIdx < len) {
					// insert hard line break at l.
					string.insert(lIdx, "\n");
					cWidth = 0;
				} else {
					rIdx = lIdx + 1;
				}
			} while(rIdx == -1);
			if( lIdx < len && string[lIdx] == ' ' ) {
				cWidth += handle.font->glyphs[0].GetAdvance();
			}
			// 1.2 move right iterator until space is reached or width is exceeded.
			float rWidth = 0;
			while(rIdx < len) {
				float charWidth = handle.font->glyphs[ string[rIdx] - ' ' ].GetAdvance();
				cWidth += charWidth;
				rWidth += charWidth;
				if( string[rIdx] == ' ' ) {
					lIdx = rIdx;
					rWidth = 0;
				}
				
				++rIdx;
				
				if(cWidth >= w) {
					string[lIdx] = '\n';
					cWidth = rWidth;
				}
			}
		}
		
		// 2nd step: set left iterator, get width and height.
		int mLineCount = int(h / handle.font->lineHeight);
		lines = 1; // class member
		if(cursor == -1) {
			cursor = len-1;
		}
		lIdx = cursor;
		cWidth = 0;
		while(lIdx > 0 && mLineCount > 0) {
			if(string[lIdx] == '\n') {
				maxWidth = fmaxf(maxWidth, cWidth);
				cWidth = 0;
				--mLineCount;
				++lines;
			} else {
				cWidth += handle.font->glyphs[ string[lIdx] - ' ' ].GetAdvance();
			}
			if(!wrap && cWidth > w) {
				mLineCount = -1;
			}
			--lIdx;
		}
		// two class members:
		width = fmaxf(maxWidth, cWidth);
		height = float(lines) * handle.font->lineHeight;
		
		leftCursor = mLineCount == 0 ? lIdx+2 : lIdx;
		
		// 3rd step: set right iterator: advance until all space left on line is consumed.
		while(cursor <= len-1 && cWidth < w) {
			if(string[cursor] == '\n') {
				break;
			} else {
				cWidth += handle.font->glyphs[ string[cursor] - ' ' ].GetAdvance();
				++cursor;
			}
		}
		//--cursor;
		 
		rightCursor = cursor;
	}
}
